package xyz.xtt.exchange.service.impl;

import java.net.MalformedURLException;
import java.net.URL;
import java.time.Duration;
import java.util.Arrays;
import java.util.Date;
import java.util.Objects;
import java.util.function.Predicate;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import cn.hutool.core.date.DateUtil;
import lombok.extern.slf4j.Slf4j;
import xyz.xtt.exchange.constants.CheckLimitTypeEnum;
import xyz.xtt.exchange.constants.DimensionalTypeEnum;
import xyz.xtt.exchange.constants.ErrorMessage;
import xyz.xtt.exchange.exception.ChainException;
import xyz.xtt.exchange.service.CheckLimitService;
import xyz.xtt.exchange.service.ThirdService;
import xyz.xtt.exchange.vo.AuditCheckResult;

/**
 * @author huqibo
 * @date 2024/11/21
 */
@Slf4j
@RefreshScope
@Service
public class CheckLimitServiceImpl implements CheckLimitService {

	private static final String 审核不通过 = "审核不通过";

	@Autowired
	private StringRedisTemplate stringRedisTemplate;

	@Autowired
	private ThirdService thirdService;

	@Value("${asset.audit.domain}")
	private String[] auditDomain;

	private BoundHashOperations<String, String, Long> boundHashOps(String uid, CheckLimitTypeEnum limitType) {
		String daySub = DateUtil.format(new Date(), "yyyyMMdd");
		String mapKey = String.format("nft:%s:%s:%s", uid, daySub, limitType.desc());
		return stringRedisTemplate.boundHashOps(mapKey);
	}

	@Override
	public void plusDayValue(String uid, CheckLimitTypeEnum limitType, DimensionalTypeEnum dimensionalType) {
		BoundHashOperations<String, String, Long> boundHashOps = boundHashOps(uid, limitType);
		boundHashOps.increment(dimensionalType.desc(), 1);
	}

	private void resetDayValue(String uid, CheckLimitTypeEnum limitType, DimensionalTypeEnum dimensionalType) {
		BoundHashOperations<String, String, Long> boundHashOps = boundHashOps(uid, limitType);
		Long oldValue = boundHashOps.increment(dimensionalType.desc(), 0);
		if (oldValue.intValue() > 0) {
			boundHashOps.increment(dimensionalType.desc(), -1 * oldValue);
		}
	}

	private Long getRedisValue(String uid, CheckLimitTypeEnum limitType, DimensionalTypeEnum dimensionalType) {
		BoundHashOperations<String, String, Long> boundHashOps = boundHashOps(uid, limitType);
		Long val = boundHashOps.increment(dimensionalType.desc(), 0);
		boundHashOps.expire(Duration.ofHours(24));
		return val;
	}

	@Override
	public void checkLimit(String uid, CheckLimitTypeEnum limitType, Predicate<Long> dayTotalRequestCheck, Predicate<Long> daySuccessCheck,
			Predicate<Long> dayContinuousFailCheck, Predicate<Long> dayTotalFailCheck) {
		Long totalReqDayLimitVal = getRedisValue(uid, limitType, DimensionalTypeEnum.TOTAL_REQ);
		// 请求总次数
		if (dayTotalRequestCheck.test(totalReqDayLimitVal)) {
			log.info("uid:{},totalReqDayLimitVal:{}", uid, totalReqDayLimitVal);
			throw new ChainException(ErrorMessage.TOTAL_REQUSET_DAY_LIMIT);
		}
		// 每请求一次，次数加1
		plusDayValue(uid, limitType, DimensionalTypeEnum.TOTAL_REQ);
		Long daySuccessLimit = getRedisValue(uid, limitType, DimensionalTypeEnum.SUCC);
		// 成功总计数
		if (daySuccessCheck.test(daySuccessLimit)) {
			log.info("uid:{},daySuccessLimit:{}", uid, daySuccessLimit);
			throw new ChainException(ErrorMessage.DAY_SUCC_LIMIT);
		}
		// 连续错误
		Long continuousErrDayLimitVal = getRedisValue(uid, limitType, DimensionalTypeEnum.CONTINUOUS_FAIL);
		if (dayContinuousFailCheck.test(continuousErrDayLimitVal)) {
			log.info("uid:{},continuousErrDayLimitVal:{}", uid, continuousErrDayLimitVal);
			throw new ChainException(ErrorMessage.CONTINUOUS_LIMIT);
		}
		// 错误总数
		Long totalErrDayLimitVal = getRedisValue(uid, limitType, DimensionalTypeEnum.TOTAL_FAIL);
		if (dayTotalFailCheck.test(totalErrDayLimitVal)) {
			log.info("uid:{},totalErrDayLimitVal:{}", uid, totalErrDayLimitVal);
			throw new ChainException(ErrorMessage.TOTAL_ERR_DAY_LIMIT);
		}
	}

	/**
	 * 内容审核
	 * 
	 * @param uid
	 * @param context
	 * @param target
	 */
	@Override
	public AuditCheckResult auditCheck(String uid, CheckLimitTypeEnum limitType, String context, String target) {
		AuditCheckResult check = thirdService.audit(context, target, uid, limitType);
		// 图文内容审核
		if (StringUtils.isNotBlank(target) && Objects.equals(check.isImgResult(), false)) {
			// 每错一次错误总数加1
			plusDayValue(uid, limitType, DimensionalTypeEnum.TOTAL_FAIL);
			// 每错误一次加1
			plusDayValue(uid, limitType, DimensionalTypeEnum.CONTINUOUS_FAIL);
			throw new ChainException(ErrorMessage.IMAGE_FAIL);
		} else if (StringUtils.isNotBlank(target) && Objects.equals(check.isTxtResult(), false)) {
//			// 每错一次错误总数加1
			plusDayValue(uid, limitType, DimensionalTypeEnum.TOTAL_FAIL);
//			// 每错误一次加1
			plusDayValue(uid, limitType, DimensionalTypeEnum.CONTINUOUS_FAIL);
			throw new ChainException(ErrorMessage.TEXT_FAIL);
		}
		// 重置连续错误
		resetDayValue(uid, limitType, DimensionalTypeEnum.CONTINUOUS_FAIL);
		return check;
	}

	/**
	 * 域名检验
	 * 
	 * @param url
	 * @return
	 */
	@Override
	public boolean checkDomain(String targetUrl) {
		try {
			URL url = new URL(targetUrl);
			String host = url.getHost();
			return Arrays.asList(auditDomain).stream().anyMatch(x -> Objects.equals(host, x));
		} catch (MalformedURLException e) {
		}
		return false;
	}

	@Override
	public long successTotal(String uid, CheckLimitTypeEnum limitType) {
		return getRedisValue(uid, limitType, DimensionalTypeEnum.SUCC);
	}
}
